﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.Generic;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;

namespace DC.ControlLibrary
{
    public class CoverFlowControl : ListBox
    {
        private Orientation orientation = Orientation.Horizontal;
        public Orientation Orientation
        {
            get { return orientation; }
            set { orientation = value; }
        }
        private List<ListBoxItem> items;
        private List<ListBoxItem> tempItems;
        private double k { get { return SpaceBetweenSelectedItemAndItems; } }
        private double l { get { return SpaceBetweenItems; } }
        private double r { get { return RotationAngle; } }
        private double z { get { return ZDistance; } }
        private double s { get { return Scale; } }
        private int si;
        private int pageCount = 0;
        private Storyboard sb;
        private DoubleAnimation asiAnimation;
        private Duration duration = new Duration(TimeSpan.FromSeconds(.5));

        private Point itemRemderTransformOrigin = new Point(.5, .5);

        public event EventHandler AnimationEnded;
        public bool Animate { get; set; }
        public Point ItemRenderTransformOrigin 
        {
            get
            {
                return itemRemderTransformOrigin;
            }
            set
            {
                itemRemderTransformOrigin = value;
                foreach(ListBoxItem item in items)
                    item.RenderTransformOrigin = itemRemderTransformOrigin;
            }
        }

        #region DependancyProperties

        public double ASI
        {
            get
            {
                return (double)GetValue(ASIProperty);
            }
            set
            {
                SetValue(ASIProperty, value);
            }
        }
        public static readonly DependencyProperty ASIProperty =
            DependencyProperty.Register("ASIProperty", typeof(double), typeof(CoverFlowControl), new PropertyMetadata(0d, new PropertyChangedCallback(CoverFlowControl.OnASIChanged)));
        private static void OnASIChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CoverFlowControl c = d as CoverFlowControl;
            c.SetItemPositions();
        }

        public double SpaceBetweenItems
        {
            get { return (double)GetValue(SpaceBetweenItemsProperty); }
            set { SetValue(SpaceBetweenItemsProperty, value); }
        }
        public static readonly DependencyProperty SpaceBetweenItemsProperty =
            DependencyProperty.Register("SpaceBetweenItems", typeof(double), typeof(CoverFlowControl), new PropertyMetadata(100d, new PropertyChangedCallback(CoverFlowControl.OnValuesChanged)));

        public double SpaceBetweenSelectedItemAndItems
        {
            get { return (double)GetValue(SpaceBetweenSelectedItemAndItemsProperty); }
            set { SetValue(SpaceBetweenSelectedItemAndItemsProperty, value); }
        }
        public static readonly DependencyProperty SpaceBetweenSelectedItemAndItemsProperty =
            DependencyProperty.Register("SpaceBetweenSelectedItemAndItems", typeof(double), typeof(CoverFlowControl), new PropertyMetadata(250d, new PropertyChangedCallback(CoverFlowControl.OnValuesChanged)));

        public double RotationAngle
        {
            get { return (double)GetValue(RotationAngleProperty); }
            set { SetValue(RotationAngleProperty, value); }
        }
        public static readonly DependencyProperty RotationAngleProperty =
            DependencyProperty.Register("RotationAngle", typeof(double), typeof(CoverFlowControl), new PropertyMetadata(45d, new PropertyChangedCallback(CoverFlowControl.OnValuesChanged)));

        public double ZDistance
        {
            get { return (double)GetValue(ZDistanceProperty); }
            set { SetValue(ZDistanceProperty, value); }
        }
        public static readonly DependencyProperty ZDistanceProperty =
            DependencyProperty.Register("ZDistance", typeof(double), typeof(CoverFlowControl), new PropertyMetadata(0.0d, new PropertyChangedCallback(CoverFlowControl.OnValuesChanged)));

        public double Scale
        {
            get { return (double)GetValue(ScaleProperty); }
            set { SetValue(ScaleProperty, value); }
        }
        public static readonly DependencyProperty ScaleProperty =
            DependencyProperty.Register("Scale", typeof(double), typeof(CoverFlowControl), new PropertyMetadata(.7d, new PropertyChangedCallback(CoverFlowControl.OnValuesChanged)));

        public IEasingFunction EasingFunction
        {
            get { return (IEasingFunction)GetValue(EasingFunctionProperty); }
            set { SetValue(EasingFunctionProperty, value); }
        }
        public static readonly DependencyProperty EasingFunctionProperty =
            DependencyProperty.Register("EasingFunction", typeof(IEasingFunction), typeof(CoverFlowControl), null);

        public Duration Duration
        {
            get { return duration; }
            set
            {
                duration = value;
                asiAnimation.Duration = duration;
                sb.Duration = new Duration(duration.TimeSpan.Add(TimeSpan.FromSeconds(.2)));
            }
        }

        private static void OnValuesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as CoverFlowControl).SetItemPositions();
        }


        #endregion

        public CoverFlowControl()
        {
            DefaultStyleKey = typeof(CoverFlowControl);
            items = new List<ListBoxItem>();
            tempItems = new List<ListBoxItem>();

            asiAnimation = new DoubleAnimation();
            asiAnimation.Duration = duration;
            asiAnimation.EasingFunction = new CubicEase();

            sb = new Storyboard();
            sb.Duration = new Duration(duration.TimeSpan.Add(TimeSpan.FromSeconds(.2)));
            sb.Children.Add(asiAnimation);
            Storyboard.SetTarget(asiAnimation, this);

            Storyboard.SetTargetProperty(asiAnimation, new PropertyPath("ASIProperty"));
            sb.Completed += new EventHandler(sb_Completed);

            Animate = true;

            Resources.Add("ASISb", sb);


            SizeChanged += new SizeChangedEventHandler(CoverFlowControl_SizeChanged);
            SelectionChanged += new SelectionChangedEventHandler(CoverFlowControl_SelectionChanged);
        }

        protected override void OnMouseWheel(MouseWheelEventArgs e)
        {
            base.OnMouseWheel(e);
            int i = -(e.Delta / 120);

            int index = SelectedIndex + i;
            if (index < 0)
                First();
            else if (index > Items.Count - 1)
                Last();
            else
                SelectedIndex = index;
            e.Handled = true;
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            //base.OnKeyDown(e);
            switch (e.Key)
            {
                case Key.End:
                    Last();
                    e.Handled = true;
                    break;
                case Key.Home:
                    First();
                    e.Handled = true;
                    break;
                case Key.Left:
                    if (Orientation == Orientation.Horizontal)
                    {
                        Prev();
                        e.Handled = true;
                    }
                    break;
                case Key.PageDown:
                    PageDown();
                    e.Handled = true;
                    break;
                case Key.PageUp:
                    PageUp();
                    e.Handled = true;
                    break;
                case Key.Right:
                    if (Orientation == Orientation.Horizontal)
                    {
                        Next();
                        e.Handled = true;
                    }
                    break;
                case Key.Up:
                    if (Orientation == Orientation.Vertical)
                    {
                        Prev();
                        e.Handled = true;
                    }
                    break;
                case Key.Down:
                    if (Orientation == Orientation.Vertical)
                    {
                        Next();
                        e.Handled = true;
                    }
                    break;
                default:
                    break;
            }
        }

        void sb_Completed(object sender, EventArgs e)
        {
            if (AnimationEnded != null)
                AnimationEnded(this, null);
        }

        public void SetItemPositions()
        {
            foreach (ListBoxItem item in tempItems)
                item.Visibility = Visibility.Collapsed;
            tempItems.Clear();

            if (items.Count == 0)
                return;

            si = (int)Math.Round(ASI, 0);
            if(si < 0)
                return;
            for (int x = 0; x <= pageCount + 1; x++)
            {
                if (x == 0)
                {
                    SetLocation(items[si], si);
                    continue;
                }
                if (si - x > -1)
                    SetLocation(items[si - x], si - x);
                if (si + x < items.Count)
                    SetLocation(items[si + x], si + x);
            }

        }

        private void SetLocation(ListBoxItem item, int index)
        {
            item.Visibility = Visibility.Visible;

            tempItems.Add(item);

            double t = -(ASI - index);
            double tk = k;
            double tr = r;
            double ts = s;
            double tz = z;
            int d = index - si;

            if (t < 1 && t > -1)
            {
                tk *= t;
                tr *= t;
                double tab = Math.Abs(t);
                ts += (1 - s) * (1 - tab);
                tz *= tab;
            }
            else if (t > 0)
            {
                tk += l * (t - 1);
            }
            else if (t < 0)
            {
                tk = -tk + (l * (t + 1));
                tr = -tr;
                d = si - index;
            }

            ((ScaleTransform)((TransformGroup)item.RenderTransform).Children[0]).ScaleX = ts;
            ((ScaleTransform)((TransformGroup)item.RenderTransform).Children[0]).ScaleY = ts;

            if (Orientation == Orientation.Horizontal)
                ((TranslateTransform)((TransformGroup)item.RenderTransform).Children[1]).X = tk;
            else
                ((TranslateTransform)((TransformGroup)item.RenderTransform).Children[1]).Y = tk;

            if (Orientation == Orientation.Horizontal)
                ((PlaneProjection)item.Projection).RotationY = tr;
            else
                ((PlaneProjection)item.Projection).RotationX = tr;
            ((PlaneProjection)item.Projection).LocalOffsetZ = tz;

            Canvas.SetZIndex(item, -d);
        }

        void CoverFlowControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (Animate)
            {
                double t = ASI;
                sb.Stop();
                asiAnimation.From = t;
                asiAnimation.To = SelectedIndex;
                sb.Begin();
            }
            else
            {
                ASI = SelectedIndex;
            }
        }

        void CoverFlowControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            RectangleGeometry visibleArea = new RectangleGeometry();
            Rect clip = new Rect(0, 0, ActualWidth, ActualHeight);
            visibleArea.Rect = clip;
            Clip = visibleArea;

            if (Orientation == Orientation.Horizontal)
                pageCount = (int)Math.Ceiling(((ActualWidth / 2) - k) / l);
            else
                pageCount = (int)Math.Ceiling(((ActualHeight / 2) - k) / l);

            SetItemPositions();
        }

        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            base.PrepareContainerForItemOverride(element, item);
            ListBoxItem item2 = element as ListBoxItem;
            if (item2 != null && !items.Contains(item2))
            {
                item2.Visibility = Visibility.Collapsed;

                if (Orientation == Orientation.Horizontal)
                    item2.HorizontalAlignment = HorizontalAlignment.Center;
                else
                    item2.VerticalAlignment = VerticalAlignment.Center;

                items.Add(item2);

                TransformGroup myTransformGroup = new TransformGroup();
                ScaleTransform scaleTransform = new ScaleTransform();
                TranslateTransform translateTransform = new TranslateTransform();
                PlaneProjection planeProjection = new PlaneProjection() { CenterOfRotationX = .5, CenterOfRotationY = .5, CenterOfRotationZ = .5 };

                myTransformGroup.Children.Add(scaleTransform);
                myTransformGroup.Children.Add(translateTransform);

                // Associate the transforms to the object 
                item2.RenderTransformOrigin = itemRemderTransformOrigin;
                item2.RenderTransform = myTransformGroup;
                item2.Projection = planeProjection;

                //SetLocation(item2, items.Count - 1);
                if (items.Count < pageCount + 1)
                {
                    SetLocation(item2, items.Count - 1);
                }

            }
        }

        protected override void ClearContainerForItemOverride(DependencyObject element, object item)
        {
            base.ClearContainerForItemOverride(element, item);
            ListBoxItem item2 = element as ListBoxItem;
            items.Remove(item2);
        }

        public void Next()
        {
            if (SelectedIndex < Items.Count - 1)
                SelectedIndex++;
            else
                SelectedIndex = 0;
        }
        public void Prev()
        {
            if (SelectedIndex > 0)
                SelectedIndex--;
            else
                SelectedIndex = Items.Count - 1;
        }
        public void PageUp()
        {
            int temp = SelectedIndex - pageCount - 1;
            if (temp < 0)
                temp = 0;
            SelectedIndex = temp;
        }
        public void PageDown()
        {
            int temp = SelectedIndex + pageCount + 1;
            if (temp > Items.Count - 1)
                temp = Items.Count - 1;
            SelectedIndex = temp;
        }
        public void First()
        {
            SelectedIndex = 0;
        }
        public void Last()
        {
            SelectedIndex = Items.Count - 1;
        }
    }
}
